function defineReactive(obj, key, val) {
  observe(val);
  // 每一次执行的时候都会创建一个管家
  const dep = new Dep();

  Object.defineProperty(obj, key, {
    get() {
      // 读数据的时候进行依赖收集
      Dep.target && dep.addDep(Dep.target);
      return val;
    },
    set(newVal) {
      if (newVal !== val) {
        observe(newVal);
        val = newVal;
        // 通知下去
        dep.notify();
      }
    },
  });
}

function observe(obj) {
  if (typeof obj !== 'object' || obj == null) {
    return;
  }

  new Observer(obj);
}

function proxy(vm) {
  Object.keys(vm.$data).forEach((key) => {
    Object.defineProperty(vm, key, {
      get() {
        return vm.$data[key];
      },
      set(val) {
        vm.$data[key] = val;
      },
    });
  });
}

class YLVue {
  constructor(options) {
    // 保存选项
    this.$options = options;
    this.$data = options.data;

    // 响应化处理
    observe(this.$data);

    // 代理  吧 data中的key映射到 this上，并且 this[key] 和 this.$data[key] 做关联
    proxy(this);

    // 编译  解析 dom 元素  将 this 和 dom 做一个关联
    new Compiler(this.$options.el, this);
  }
}

// 每一个响应式的对象 都 伴生一个 Observer 对象
class Observer {
  constructor(value) {
    this.value = value;

    // 判断value 是什么类型  做响应式处理
    if (typeof value === 'object') {
      this.walk(value);
    }
  }
  walk(obj) {
    Object.keys(obj).forEach((key) => {
      defineReactive(obj, key, obj[key]);
    });
  }
}

// 编译过程
class Compiler {
  // 一个元素，this实例
  constructor(el, vm) {
    this.$el = document.querySelector(el);
    this.$vm = vm;

    // 编译模板
    if (this.$el) {
      this.compile(this.$el);
    }
  }

  // 做编译的   分两个  编译 dom元素 和 编译属性
  compile(el) {
    // childNodes 获取所有的节点
    el.childNodes.forEach((node) => {
      // 判断类型
      if (this.isElement(node)) {
        // console.log('编译元素  -->', node.nodeName);
        this.compileElement(node);
      } else if (this.isInter(node)) {
        // console.log('编译插值表达式  -->', node.textContent);
        this.compileText(node);
      }
      // 递归遍历
      if (node.childNodes) {
        this.compile(node);
      }
    });
  }

  // 对 差值文本进行编译
  compileText(node) {
    this.update(node, RegExp.$1, 'text');
    // node.textContent = this.$vm[RegExp.$1];
  }

  // 对 dom 元素 进行编译
  compileElement(node) {
    // 获取属性
    const nodeAttrs = node.attributes;
    Array.from(nodeAttrs).forEach((attr) => {
      // console.log('attr  -->', attr);
      const attrName = attr.name; // yl-xxx
      const exp = attr.value; // aaa
      if (this.isDirective(attrName)) {
        //  yl-xxx = 'aaa'
        const dir = attrName.substring(3);
        // 执行这个指令
        this[dir] && this[dir](node, exp);
      } else if (this.isEvent(attrName)) {
        //  @click='methodName'
        const dir = attrName.substring(1);
        // 做事件监听
        this.eventHandler(node, exp, dir);
      }
    });
  }

  eventHandler(node, exp, dir) {
    node.addEventListener(dir, this.$vm.$options.methods[exp].bind(this.$vm));
  }

  // yl-text 指令
  text(node, exp) {
    this.update(node, exp, 'text');
  }
  textUpdate(node, value) {
    node.textContent = value;
  }

  // yl-html 指令
  html(node, exp) {
    this.update(node, exp, 'html');
  }
  htmlUpdate(node, value) {
    node.innerHTML = value;
  }

  // yl-model 指令
  model(node, exp) {
    // update 方法只完成的赋值  没有做事件监听
    this.update(node, exp, 'model');
    // input事件
    node.addEventListener('input', (e) => {
      this.$vm[exp] = e.target.value;
    });
  }
  modelUpdate(node, value) {
    node.value = value;
  }

  // 创建 Watcher 肯定是在编译的时候 使用到 key的时候就创建
  // 定义一个和创建函数  来复用
  // 所有的动态绑定都需要创建更新函数和对应的 Watcher
  update(node, exp, dir) {
    // 初始化   dir + 'Update'  这个指令对应的更新函数
    // 更新函数的 value是当前最新的值
    const fn = this[dir + 'Update'];
    fn && fn(node, this.$vm[exp]);
    //  更新
    new Watcher(this.$vm, exp, (val) => {
      fn && fn(node, val);
    });
  }

  // 判断一个属性是不是 自定义的指令
  isDirective(attrName) {
    return /^yl-/.test(attrName);
  }

  // 判断一个属性是不是事件
  isEvent(attrName) {
    return /^@/.test(attrName);
  }

  // 判断 是不是dom元素
  isElement(node) {
    return node.nodeType === 1;
  }

  // 判断 是不是 插值表达式
  isInter(node) {
    return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
  }
}

//  类似小秘书  视图中每有一个 和data绑定的依赖 就会有一个 wetcher
class Watcher {
  // 实例  关联的key值  触发的更新函数
  constructor(vm, key, updateFn) {
    this.$vm = vm;
    this.$key = key;
    this.$updateFn = updateFn;

    // 读一次数据 就会触发 defineReactive 中的get方法
    Dep.target = this;
    this.$vm[key]; // 触发了get  就触发了依赖收集
    Dep.target = null;
  }

  update() {
    this.$updateFn.call(this.$vm, this.$vm[this.$key]);
  }
}

// 大管家  管理小秘书，每一个data中的响应式的key都对应一个管家(每一次调用defineReactive 创建管家)，变化之后会通知对应的小秘书干活(set被执行了就会通知下去)
class Dep {
  constructor() {
    this.deps = [];
  }
  addDep(watcher) {
    this.deps.push(watcher);
  }

  notify() {
    this.deps.forEach((watcher) => watcher.update());
  }
}
